<!DOCTYPE html>
<html lang="en" data-theme=""><head>
    <title> Rover | context 标准库 </title>

    
    <meta charset="utf-8"><meta name="generator" content="Hugo 0.79.1" /><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
    <meta name="description" content="代码如诗，生命如虹">
    
    <link rel="stylesheet"
          href="/css/style.min.39603a6316177eb06681d6e6ea0570fd106ead89cd6079055ca060feb76c7f83.css"
          integrity="sha256-OWA6YxYXfrBmgdbm6gVw/RBurYnNYHkFXKBg/rdsf4M="
          crossorigin="anonymous"
          type="text/css">
    
    <link rel="stylesheet"
        href="/css/markupHighlight.min.cc84ed683057cc175ddfa738ea6ba2d5c882b95cb64f50bf9be918cb3791887b.css"
        integrity="sha256-zITtaDBXzBdd36c46mui1ciCuVy2T1C/m&#43;kYyzeRiHs="
        crossorigin="anonymous"
        type="text/css">
    
    <link rel="stylesheet" 
    href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" 
    integrity="sha512-+4zCK9k+qNFUR5X+cKL9EIR+ZOhtIloNl9GIKS57V1MyNsYpYcUrUeQc9vNfzsWfV28IaLL3i96P9sdNyeRssA==" 
    crossorigin="anonymous" />

    
    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
    <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
    <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
    <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">

    <link rel="canonical" href="/posts/context/">

    
    
    
    
    <script type="text/javascript"
            src="/js/anatole-header.min.d8599ee07b7d3f11bafbac30657ccc591e8d7fd36a9f580cd4c09e24e0e4a971.js"
            integrity="sha256-2Fme4Ht9PxG6&#43;6wwZXzMWR6Nf9Nqn1gM1MCeJODkqXE="
            crossorigin="anonymous"></script>


    
    <meta name="twitter:card" content="summary"/>
<meta name="twitter:title" content="context 标准库"/>
<meta name="twitter:description" content="注：本文参考摘抄于《Go语言核心编程》
 Go 中的goroutine 之间并没有父与子的关系，也就是没有所谓子进程退出后的通知机制，多个goroutine都是平行地被调度，多个goroutine如何协作设计通信、同步、通知和退出四个方面。
通信： chan通道当然是goroutine 之间通信的基础。注意这里的通信主要是指程序的数据通信。
同步: 不带缓冲的chan提供了一个天然的同步等待机制；当然sync.WaitGroup也为多个goroutine协同工作提供一种同步等待机制。
通知： 这个通知和上面通信的数据不一样，通知通常不是业务数据，而是管理、控制流数据。要处理这个也有方法，在输入端绑定两个chan，一个用于业务流数据，另一个用于异常通信数据，然后使用select收敛进行处理。这个方案可以解决简单的问题，但不是一个通用的解决方案。
退出: goroutine 之间没有父子关系，如何通知goroutine退出？可以通过增加一个单独的通道，借助通道和select的广播机制（close channel to broadcast） 实现退出。
Go 语言在语法上处理某个goroutine退出通知机制很简单。但是遇到复杂的并发结构处理起来就显得力不从心。实际编程中goroutine会拉起新的goroutine，新的goroutine又会拉起另外一个新的goroutine，最终形成一个树状的结构。由于goroutine里并没有父子概念，这个树状结构知识在程序员头脑中抽象出来的，程序的执行模型并没有维护这么一个树状结构。
怎么通知这个树状上所有的goroutine退出？仅依靠语法层面的支持显然比较难处理。为此Go1.7 提供了一个标准库context来解决这个问题。它提供两种功能：退出通知和 元数据传递。context库的设计目的就是跟踪goroutine调用，在其内部维护一个调用树。并在这些调用树中传递通知和元数据。
一、context 的设计目的 context库的目的就是跟踪goroutine的调用树，并在这些goroutine 调用树中传递通知和元数据。两个目的：
 退出通知机制 —— 通知可以传递给整个goroutine调用树上的每一个goroutine。 传递数据—— 数据可以传递给整个goroutine调用树上的goroutine。  二、基本数据结构 在介绍context包之前，先理解context包的整体工作机制：
第一个创建Context 的goroutine 被称之为root节点。
root节点负责创建一个实现Context接口的具体对象，并将该对象作为参数传递到其新拉起的goroutine中，下有的goroutine可以继续封装该对象，再传递到更下游的goroutine。
Context对象在传递的过程中最终形成一个树状的数据结构，这样通过位于root节点（树的根节点）的Context对象就能遍历整个Context 对象树，通知和消息就可以通过root节点传递出去。实现了上游goroutine 对下游goroutine 的消息传递。
Context 接口 Context 是一个基本接口，所有的Context 对象都要实现该接口，context 的使用者在调用接口中都使用Context 作为参数类型。
type Context interface { //如果Context实现了超时控制，则该方法返回ok true, deadline 为超时时间。 	Deadline() (deadline time.Time, ok bool) //后端被调用的goroutine应该监听该方法返回的chan，以便及时释放资源 	Done() &lt;-chan struct{} //Done 返回的chan 收到通知的时候，才可以访问Err() 获知为什么原因被取消 	Err() error //可以访问上游goroutine传递给下游goroutine 的值。 	Value(key interface{}) interface{} } canceler 接口 canceler 接口是一个扩展接口，规定了取消通知的Context具体类型需要实现的接口。context包中的具体类型*cancelCtx 和*timeCtx 都实现了该接口。如下："/>


    

</head>
<body><div class="sidebar . ">
    <div class="logo-title">
        <div class="title">
            <img src="/images/wx_avatar.jpeg" alt="profile picture">
            <h3 title=""><a href="/">I&#39;m Rover</a></h3>
            <div class="description">
                <p>代码如诗，生命如虹</p>
            </div>
        </div>
    </div>
    <ul class="social-links">
        
    </ul>
    <div class="footer">
        <div class="by_farbox">&copy; Rover  2020-2021 </div>
    </div>
</div>
<div class="main">
    <div class="page-top  . ">
    <a role="button" class="navbar-burger" data-target="navMenu" aria-label="menu" aria-expanded="false">
        <span aria-hidden="true"></span>
        <span aria-hidden="true"></span>
        <span aria-hidden="true"></span>
    </a>
    <ul class="nav" id="navMenu">
        
        
            
            <li><a 
                   href="/"
                        
                   title="">Home</a></li>
        
        
        
    </ul>
</div>

    <div class="autopagerize_page_element">
        <div class="content">
    <div class="post  . ">
        <div class="post-content">
            
            <div class="post-title">
                <h3>context 标准库</h3>
                
            </div>

            <blockquote>
<p>注：本文参考摘抄于《Go语言核心编程》</p>
</blockquote>
<p>Go 中的goroutine 之间并没有父与子的关系，也就是没有所谓子进程退出后的通知机制，多个goroutine都是平行地被调度，多个goroutine如何协作设计通信、同步、通知和退出四个方面。</p>
<p><strong>通信：</strong> chan通道当然是goroutine 之间通信的基础。注意这里的通信主要是指程序的数据通信。</p>
<p><strong>同步:</strong> 不带缓冲的chan提供了一个天然的同步等待机制；当然<code>sync.WaitGroup</code>也为多个goroutine协同工作提供一种同步等待机制。</p>
<p><strong>通知：</strong> 这个通知和上面通信的数据不一样，通知通常不是业务数据，而是管理、控制流数据。要处理这个也有方法，在输入端绑定两个chan，一个用于业务流数据，另一个用于异常通信数据，然后使用select收敛进行处理。这个方案可以解决简单的问题，但不是一个通用的解决方案。</p>
<p><strong>退出:</strong> goroutine 之间没有父子关系，如何通知goroutine退出？可以通过增加一个单独的通道，借助通道和select的广播机制（close channel to broadcast） 实现退出。</p>
<p>Go 语言在语法上处理某个goroutine退出通知机制很简单。但是遇到复杂的并发结构处理起来就显得力不从心。实际编程中goroutine会拉起新的goroutine，新的goroutine又会拉起另外一个新的goroutine，最终形成一个树状的结构。由于goroutine里并没有父子概念，这个树状结构知识在程序员头脑中抽象出来的，程序的执行模型并没有维护这么一个树状结构。</p>
<p>怎么通知这个树状上所有的goroutine退出？仅依靠语法层面的支持显然比较难处理。为此Go1.7 提供了一个标准库context来解决这个问题。它提供两种功能：<strong>退出通知</strong>和 <strong>元数据传递</strong>。context库的设计目的就是跟踪goroutine调用，在其内部维护一个调用树。并在这些调用树中传递通知和元数据。</p>
<h2 id="一context-的设计目的">一、context 的设计目的</h2>
<p>context库的目的就是跟踪goroutine的调用树，并在这些goroutine 调用树中传递通知和元数据。两个目的：</p>
<ul>
<li>退出通知机制 —— 通知可以传递给整个goroutine调用树上的每一个goroutine。</li>
<li>传递数据—— 数据可以传递给整个goroutine调用树上的goroutine。</li>
</ul>
<h2 id="二基本数据结构">二、基本数据结构</h2>
<p>在介绍context包之前，先理解context包的整体工作机制：</p>
<p>第一个创建Context 的goroutine 被称之为root节点。</p>
<p>root节点负责创建一个实现Context接口的具体对象，并将该对象作为参数传递到其新拉起的goroutine中，下有的goroutine可以继续封装该对象，再传递到更下游的goroutine。</p>
<p>Context对象在传递的过程中最终形成一个树状的数据结构，这样通过位于root节点（树的根节点）的Context对象就能遍历整个Context 对象树，通知和消息就可以通过root节点传递出去。实现了上游goroutine 对下游goroutine 的消息传递。</p>
<h3 id="context-接口">Context 接口</h3>
<p>Context 是一个基本接口，所有的Context 对象都要实现该接口，context 的使用者在调用接口中都使用Context 作为参数类型。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go">	<span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Context</span> <span style="color:#66d9ef">interface</span> {
	  <span style="color:#75715e">//如果Context实现了超时控制，则该方法返回ok true, deadline 为超时时间。
</span><span style="color:#75715e"></span>		<span style="color:#a6e22e">Deadline</span>() (<span style="color:#a6e22e">deadline</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span>, <span style="color:#a6e22e">ok</span> <span style="color:#66d9ef">bool</span>)
		
		<span style="color:#75715e">//后端被调用的goroutine应该监听该方法返回的chan，以便及时释放资源
</span><span style="color:#75715e"></span>		<span style="color:#a6e22e">Done</span>() <span style="color:#f92672">&lt;-</span><span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">struct</span>{}
    
    <span style="color:#75715e">//Done 返回的chan 收到通知的时候，才可以访问Err() 获知为什么原因被取消
</span><span style="color:#75715e"></span>		<span style="color:#a6e22e">Err</span>() <span style="color:#66d9ef">error</span>
    
    <span style="color:#75715e">//可以访问上游goroutine传递给下游goroutine 的值。
</span><span style="color:#75715e"></span>		<span style="color:#a6e22e">Value</span>(<span style="color:#a6e22e">key</span> <span style="color:#66d9ef">interface</span>{}) <span style="color:#66d9ef">interface</span>{}
	}
</code></pre></div><h3 id="canceler-接口">canceler 接口</h3>
<p>canceler 接口是一个扩展接口，规定了取消通知的Context具体类型需要实现的接口。context包中的具体类型<code>*cancelCtx</code> 和<code>*timeCtx</code> 都实现了该接口。如下：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#75715e">//一个context对象如果实现了canceler 接口，则可以被取消。
</span><span style="color:#75715e"></span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">canceler</span> <span style="color:#66d9ef">interface</span> {
  <span style="color:#75715e">//创建cancel接口实例的goroutine 调用cancel方法通知后续创建的goroutine退出
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">cancel</span>(<span style="color:#a6e22e">removeFromParent</span> <span style="color:#66d9ef">bool</span>, <span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>)
	
  <span style="color:#75715e">//Done 方法返回的chan需要后端的goroutine 来监听，并及时退出
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">Done</span>() <span style="color:#f92672">&lt;-</span><span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">struct</span>{}
}
</code></pre></div><h3 id="empty-context-结构">empty Context 结构</h3>
<p>emptyCtx 实现了Context 接口，但不具备任何功能。因为其所有的额方法都是空实现。其存在的目的是作为Context对象树的根（root节点）。因为context包的使用思路就是不停地调用context包提供的包装函数来创建具有特殊功能的Context实例，每一个Context 实例的创建都以上一个Context对象为参数，最终形成一个树状结构。如下</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#75715e">//emptyCtx 实现了Context 接口
</span><span style="color:#75715e">// An emptyCtx is never canceled, has no values, and has no deadline. It is not
</span><span style="color:#75715e">// struct{}, since vars of this type must have distinct addresses.
</span><span style="color:#75715e"></span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">emptyCtx</span> <span style="color:#66d9ef">int</span>

<span style="color:#66d9ef">func</span> (<span style="color:#f92672">*</span><span style="color:#a6e22e">emptyCtx</span>) <span style="color:#a6e22e">Deadline</span>() (<span style="color:#a6e22e">deadline</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span>, <span style="color:#a6e22e">ok</span> <span style="color:#66d9ef">bool</span>) {
	<span style="color:#66d9ef">return</span>
}

<span style="color:#66d9ef">func</span> (<span style="color:#f92672">*</span><span style="color:#a6e22e">emptyCtx</span>) <span style="color:#a6e22e">Done</span>() <span style="color:#f92672">&lt;-</span><span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">struct</span>{} {
	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
}

<span style="color:#66d9ef">func</span> (<span style="color:#f92672">*</span><span style="color:#a6e22e">emptyCtx</span>) <span style="color:#a6e22e">Err</span>() <span style="color:#66d9ef">error</span> {
	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
}

<span style="color:#66d9ef">func</span> (<span style="color:#f92672">*</span><span style="color:#a6e22e">emptyCtx</span>) <span style="color:#a6e22e">Value</span>(<span style="color:#a6e22e">key</span> <span style="color:#66d9ef">interface</span>{}) <span style="color:#66d9ef">interface</span>{} {
	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
}

<span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">e</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">emptyCtx</span>) <span style="color:#a6e22e">String</span>() <span style="color:#66d9ef">string</span> {
	<span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">e</span> {
	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">background</span>:
		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;context.Background&#34;</span>
	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">todo</span>:
		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;context.TODO&#34;</span>
	}
	<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;unknown empty Context&#34;</span>
}
</code></pre></div><p>package 定义了两个全局变量和两个封装函数，返回两个emptyCtx实例对象，实际使用时通过调用这两个封装函数来构造Context 的root节点。 如下：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">var</span> (
	<span style="color:#a6e22e">background</span> = new(<span style="color:#a6e22e">emptyCtx</span>)
	<span style="color:#a6e22e">todo</span>       = new(<span style="color:#a6e22e">emptyCtx</span>)
)

<span style="color:#75715e">// Background returns a non-nil, empty Context. It is never canceled, has no
</span><span style="color:#75715e">// values, and has no deadline. It is typically used by the main function,
</span><span style="color:#75715e">// initialization, and tests, and as the top-level Context for incoming
</span><span style="color:#75715e">// requests.
</span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Background</span>() <span style="color:#a6e22e">Context</span> {
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">background</span>
}

<span style="color:#75715e">// TODO returns a non-nil, empty Context. Code should use context.TODO when
</span><span style="color:#75715e">// it&#39;s unclear which Context to use or it is not yet available (because the
</span><span style="color:#75715e">// surrounding function has not yet been extended to accept a Context
</span><span style="color:#75715e">// parameter).
</span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TODO</span>() <span style="color:#a6e22e">Context</span> {
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">todo</span>
}
</code></pre></div><h3 id="cancelctx">cancelCtx</h3>
<p>cancelCtx 是一个实现了Context 接口的具体类型，同时实现了canceler接口。canceler 具有退出通知方法。注意退出通知机制不但能够通知自己，也能够逐层通知其children 节点。如下</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#75715e">// A cancelCtx can be canceled. When canceled, it also cancels any children
</span><span style="color:#75715e">// that implement canceler.
</span><span style="color:#75715e"></span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">cancelCtx</span> <span style="color:#66d9ef">struct</span> {
	<span style="color:#a6e22e">Context</span>

	<span style="color:#a6e22e">mu</span>       <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">Mutex</span>            <span style="color:#75715e">// protects following fields
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">done</span>     <span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">struct</span>{}         <span style="color:#75715e">// created lazily, closed by first cancel call
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">children</span> <span style="color:#66d9ef">map</span>[<span style="color:#a6e22e">canceler</span>]<span style="color:#66d9ef">struct</span>{} <span style="color:#75715e">// set to nil by the first cancel call
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">err</span>      <span style="color:#66d9ef">error</span>                 <span style="color:#75715e">// set to non-nil by the first cancel call
</span><span style="color:#75715e"></span>}

<span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">cancelCtx</span>) <span style="color:#a6e22e">Value</span>(<span style="color:#a6e22e">key</span> <span style="color:#66d9ef">interface</span>{}) <span style="color:#66d9ef">interface</span>{} {
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">key</span> <span style="color:#f92672">==</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">cancelCtxKey</span> {
		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>
	}
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Context</span>.<span style="color:#a6e22e">Value</span>(<span style="color:#a6e22e">key</span>)
}

<span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">cancelCtx</span>) <span style="color:#a6e22e">Done</span>() <span style="color:#f92672">&lt;-</span><span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">struct</span>{} {
	<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Lock</span>()
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">done</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">done</span> = make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">struct</span>{})
	}
	<span style="color:#a6e22e">d</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">done</span>
	<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Unlock</span>()
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">d</span>
}

<span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">cancelCtx</span>) <span style="color:#a6e22e">Err</span>() <span style="color:#66d9ef">error</span> {
	<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Lock</span>()
	<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">err</span>
	<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Unlock</span>()
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
}

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">contextName</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">string</span> {
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">s</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.(<span style="color:#a6e22e">stringer</span>); <span style="color:#a6e22e">ok</span> {
		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">String</span>()
	}
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">reflectlite</span>.<span style="color:#a6e22e">TypeOf</span>(<span style="color:#a6e22e">c</span>).<span style="color:#a6e22e">String</span>()
}

<span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">cancelCtx</span>) <span style="color:#a6e22e">String</span>() <span style="color:#66d9ef">string</span> {
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">contextName</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;.WithCancel&#34;</span>
}

<span style="color:#75715e">// cancel closes c.done, cancels each of c&#39;s children, and, if
</span><span style="color:#75715e">// removeFromParent is true, removes c from its parent&#39;s children.
</span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">cancelCtx</span>) <span style="color:#a6e22e">cancel</span>(<span style="color:#a6e22e">removeFromParent</span> <span style="color:#66d9ef">bool</span>, <span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>) {
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
		panic(<span style="color:#e6db74">&#34;context: internal error: missing cancel error&#34;</span>)
	}
	<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Lock</span>()
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Unlock</span>()
		<span style="color:#66d9ef">return</span> <span style="color:#75715e">// already canceled
</span><span style="color:#75715e"></span>	}
	<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">err</span>
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">done</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">done</span> = <span style="color:#a6e22e">closedchan</span>
	} <span style="color:#66d9ef">else</span> {
		close(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">done</span>)
	}
	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">child</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">children</span> {
		<span style="color:#75715e">// NOTE: acquiring the child&#39;s lock while holding parent&#39;s lock.
</span><span style="color:#75715e"></span>		<span style="color:#a6e22e">child</span>.<span style="color:#a6e22e">cancel</span>(<span style="color:#66d9ef">false</span>, <span style="color:#a6e22e">err</span>)
	}
	<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">children</span> = <span style="color:#66d9ef">nil</span>
	<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Unlock</span>()

	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">removeFromParent</span> {
		<span style="color:#a6e22e">removeChild</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">c</span>)
	}
}
</code></pre></div><h3 id="timectx">timeCtx</h3>
<p>timeCtx 是一个实现了Context 接口的具体类型，内部封装了cancelCtx 类型实例。同时有一个deadline变量，用来实现定时退出通知。如下：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#75715e">// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
</span><span style="color:#75715e">// implement Done and Err. It implements cancel by stopping its timer then
</span><span style="color:#75715e">// delegating to cancelCtx.cancel.
</span><span style="color:#75715e"></span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">timerCtx</span> <span style="color:#66d9ef">struct</span> {
	<span style="color:#a6e22e">cancelCtx</span>
	<span style="color:#a6e22e">timer</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Timer</span> <span style="color:#75715e">// Under cancelCtx.mu.
</span><span style="color:#75715e"></span>
	<span style="color:#a6e22e">deadline</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span>
}

<span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">timerCtx</span>) <span style="color:#a6e22e">Deadline</span>() (<span style="color:#a6e22e">deadline</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span>, <span style="color:#a6e22e">ok</span> <span style="color:#66d9ef">bool</span>) {
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">deadline</span>, <span style="color:#66d9ef">true</span>
}

<span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">timerCtx</span>) <span style="color:#a6e22e">String</span>() <span style="color:#66d9ef">string</span> {
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">contextName</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">cancelCtx</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;.WithDeadline(&#34;</span> <span style="color:#f92672">+</span>
		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">deadline</span>.<span style="color:#a6e22e">String</span>() <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34; [&#34;</span> <span style="color:#f92672">+</span>
		<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Until</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">deadline</span>).<span style="color:#a6e22e">String</span>() <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;])&#34;</span>
}

<span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">timerCtx</span>) <span style="color:#a6e22e">cancel</span>(<span style="color:#a6e22e">removeFromParent</span> <span style="color:#66d9ef">bool</span>, <span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>) {
	<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">cancelCtx</span>.<span style="color:#a6e22e">cancel</span>(<span style="color:#66d9ef">false</span>, <span style="color:#a6e22e">err</span>)
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">removeFromParent</span> {
		<span style="color:#75715e">// Remove this timerCtx from its parent cancelCtx&#39;s children.
</span><span style="color:#75715e"></span>		<span style="color:#a6e22e">removeChild</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">cancelCtx</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">c</span>)
	}
	<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Lock</span>()
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">timer</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">timer</span>.<span style="color:#a6e22e">Stop</span>()
		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">timer</span> = <span style="color:#66d9ef">nil</span>
	}
	<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Unlock</span>()
}
</code></pre></div><h3 id="valuectx">valueCtx</h3>
<p>valueCtx 是一个实现了Context 接口的具体类型，内部封装了Context接口类型。同时封装了一个k/v 的存储变量。valueCtx 可用来传递通知信息。如下：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">valueCtx</span> <span style="color:#66d9ef">struct</span> {
	<span style="color:#a6e22e">Context</span>
	<span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">val</span> <span style="color:#66d9ef">interface</span>{}
}

<span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">valueCtx</span>) <span style="color:#a6e22e">String</span>() <span style="color:#66d9ef">string</span> {
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">contextName</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;.WithValue(type &#34;</span> <span style="color:#f92672">+</span>
		<span style="color:#a6e22e">reflectlite</span>.<span style="color:#a6e22e">TypeOf</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">key</span>).<span style="color:#a6e22e">String</span>() <span style="color:#f92672">+</span>
		<span style="color:#e6db74">&#34;, val &#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">stringify</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">val</span>) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;)&#34;</span>
}

<span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">valueCtx</span>) <span style="color:#a6e22e">Value</span>(<span style="color:#a6e22e">key</span> <span style="color:#66d9ef">interface</span>{}) <span style="color:#66d9ef">interface</span>{} {
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">key</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">key</span> {
		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">val</span>
	}
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Context</span>.<span style="color:#a6e22e">Value</span>(<span style="color:#a6e22e">key</span>)
}
</code></pre></div><h2 id="三api函数">三、API函数</h2>
<p>如下两个函数是构造Context取消树的根节点对象，根节点对象用作后续With包装函数的实参。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Background</span>() <span style="color:#a6e22e">Context</span>
<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TODO</span>() <span style="color:#a6e22e">Context</span>
</code></pre></div><p>With 包装函数</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#75715e">//创建一个带有退出通知的Context具体对象。内部创建一个cancelCtx 的类型实例。
</span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">WithCancel</span>(<span style="color:#a6e22e">parent</span> <span style="color:#a6e22e">Context</span>) (<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#a6e22e">CancelFunc</span>) {}

<span style="color:#75715e">//创建一个带有超时通知的Context具体对象。内部创建一个timeCtx 的类型实例。
</span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">WithDeadline</span>(<span style="color:#a6e22e">parent</span> <span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">d</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span>) (<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">CancelFunc</span>){}

<span style="color:#75715e">//
</span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">WithTimeout</span>(<span style="color:#a6e22e">parent</span> <span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">timeout</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Duration</span>) (<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">CancelFunc</span>){}

<span style="color:#75715e">//创建一个能够传递数据的Context对象，内部创建一个valueCtx的实例类型
</span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">WithValue</span>(<span style="color:#a6e22e">parent</span> <span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">val</span> <span style="color:#66d9ef">interface</span>{}) <span style="color:#a6e22e">Context</span> {}
</code></pre></div><p>这些函数都有一个共同的特点——parent参数，其实这就是实现Context通知树的必备条件。在goroutine 调用链中，Context的实例被逐层地包装并传递，每层又可以对传进来的Context实例在封装自己所需要的功能。整个调用链树只需要一个数据结构来维护，这个维护逻辑在这些包装对象函数内部实现。</p>
<h2 id="四辅助函数">四、辅助函数</h2>
<p>With 开头的构造函数是给外部程序使用的API接口函数。Context具体对象的联调关系是在With函数内部维护的。下面展示的是With 内部使用的通用函数。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">propagateCancel</span>(<span style="color:#a6e22e">parent</span> <span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">child</span> <span style="color:#a6e22e">canceler</span>) {
	<span style="color:#a6e22e">done</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">parent</span>.<span style="color:#a6e22e">Done</span>()
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">done</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
		<span style="color:#66d9ef">return</span> <span style="color:#75715e">// parent is never canceled
</span><span style="color:#75715e"></span>	}

	<span style="color:#66d9ef">select</span> {
	<span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">done</span>:
		<span style="color:#75715e">// parent is already canceled
</span><span style="color:#75715e"></span>		<span style="color:#a6e22e">child</span>.<span style="color:#a6e22e">cancel</span>(<span style="color:#66d9ef">false</span>, <span style="color:#a6e22e">parent</span>.<span style="color:#a6e22e">Err</span>())
		<span style="color:#66d9ef">return</span>
	<span style="color:#66d9ef">default</span>:
	}

	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">p</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">parentCancelCtx</span>(<span style="color:#a6e22e">parent</span>); <span style="color:#a6e22e">ok</span> {
		<span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Lock</span>()
		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
			<span style="color:#75715e">// parent has already been canceled
</span><span style="color:#75715e"></span>			<span style="color:#a6e22e">child</span>.<span style="color:#a6e22e">cancel</span>(<span style="color:#66d9ef">false</span>, <span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">err</span>)
		} <span style="color:#66d9ef">else</span> {
			<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">children</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
				<span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">children</span> = make(<span style="color:#66d9ef">map</span>[<span style="color:#a6e22e">canceler</span>]<span style="color:#66d9ef">struct</span>{})
			}
			<span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">children</span>[<span style="color:#a6e22e">child</span>] = <span style="color:#66d9ef">struct</span>{}{}
		}
		<span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Unlock</span>()
	} <span style="color:#66d9ef">else</span> {
		<span style="color:#a6e22e">atomic</span>.<span style="color:#a6e22e">AddInt32</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">goroutines</span>, <span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>)
		<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
			<span style="color:#66d9ef">select</span> {
			<span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">parent</span>.<span style="color:#a6e22e">Done</span>():
				<span style="color:#a6e22e">child</span>.<span style="color:#a6e22e">cancel</span>(<span style="color:#66d9ef">false</span>, <span style="color:#a6e22e">parent</span>.<span style="color:#a6e22e">Err</span>())
			<span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">child</span>.<span style="color:#a6e22e">Done</span>():
			}
		}()
	}
}
</code></pre></div><ul>
<li>
<p>判断parent的方法Done 返回值是否是nil。如果是，则说明parent不是一个可取消的Context 对象，也就无所谓取消构造树。说明child 就是取消构造树的根（root）</p>
</li>
<li>
<p>如果parent 的方法Done 返回值不是nil。则向上回溯自己的祖先是否是cancelCtx 的类型实例。如果是，则将child的子节点注册维护到那棵关系树里。</p>
</li>
<li>
<p>如果向上回溯自己的祖先都不是cancelCTX类型实例，则说明整个链条的取消树是不连续的。此时只需要监听parent 和自己的取消信号即可。</p>
</li>
</ul>
<p>如下函数，判断parent中是否封装有<code>*cancelCtx</code>字段。或者接口里面存放的底层类型是否是<code>*cancelCtx</code></p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#a6e22e">parentCancelCtx</span>(<span style="color:#a6e22e">parent</span> <span style="color:#a6e22e">Context</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">cancelCtx</span>, <span style="color:#66d9ef">bool</span>) {}
</code></pre></div><p>如下函数，如果parent 封装的<code>*cancelCtx</code>类型字段，或者接口里存放的底层类型是<code>*cancelCtx</code>类型，将其构造树上的节点删除。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">removeChild</span>(<span style="color:#a6e22e">parent</span> <span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">child</span> <span style="color:#a6e22e">canceler</span>) {
	<span style="color:#a6e22e">p</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">parentCancelCtx</span>(<span style="color:#a6e22e">parent</span>)
	<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">ok</span> {
		<span style="color:#66d9ef">return</span>
	}
	<span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Lock</span>()
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">children</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
		delete(<span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">children</span>, <span style="color:#a6e22e">child</span>)
	}
	<span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Unlock</span>()
}
</code></pre></div><h2 id="五context-用法">五、context 用法</h2>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>

<span style="color:#f92672">import</span> (
	<span style="color:#e6db74">&#34;context&#34;</span>
	<span style="color:#e6db74">&#34;fmt&#34;</span>
	<span style="color:#e6db74">&#34;time&#34;</span>
)

<span style="color:#75715e">//定义一个新类型，包含一个Context 字段
</span><span style="color:#75715e"></span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">otherContext</span> <span style="color:#66d9ef">struct</span> {
	<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>
}

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#75715e">//使用context.Background 构建一个withCancel 类型的上下文
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">ctxa</span>, <span style="color:#a6e22e">work1Cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithCancel</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>())

	<span style="color:#75715e">//work模拟退出通知
</span><span style="color:#75715e"></span>	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">work</span>(<span style="color:#a6e22e">ctxa</span>, <span style="color:#e6db74">&#34;work1&#34;</span>)

	<span style="color:#75715e">//使用withDeadline 包装前面的上下文对象ctxa
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">tm</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">3</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
	<span style="color:#a6e22e">ctxb</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithDeadline</span>(<span style="color:#a6e22e">ctxa</span>, <span style="color:#a6e22e">tm</span>)

	<span style="color:#75715e">//work模拟超时通知
</span><span style="color:#75715e"></span>	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">work</span>(<span style="color:#a6e22e">ctxa</span>, <span style="color:#e6db74">&#34;work2&#34;</span>)

	<span style="color:#75715e">//使用with 对象包装前面的上下文对象ctxb
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">oc</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">otherContext</span>{<span style="color:#a6e22e">ctxb</span>}
	<span style="color:#a6e22e">ctxc</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithValue</span>(<span style="color:#a6e22e">oc</span>, <span style="color:#e6db74">&#34;key&#34;</span>, <span style="color:#e6db74">&#34;this is some things&#34;</span>)

	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">workWithValue</span>(<span style="color:#a6e22e">ctxc</span>, <span style="color:#e6db74">&#34;work3&#34;</span>)

	<span style="color:#75715e">//休眠10s,让work2 、work3 退出
</span><span style="color:#75715e"></span>
	<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">5</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)



	<span style="color:#75715e">//显示调用work1 的cancel 方法通知其退出
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">work1Cancel</span>()
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;================&#34;</span>)
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;work1 exec cancel ...&#34;</span>)
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;================&#34;</span>)
	<span style="color:#75715e">//等待work1 打印退出信息
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">2</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;all things done&#34;</span>)
}

<span style="color:#75715e">//模拟逻辑处理
</span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">work</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">name</span> <span style="color:#66d9ef">string</span>) {
	<span style="color:#66d9ef">for</span> {
		<span style="color:#66d9ef">select</span> {
			<span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Done</span>():
				<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%s get msg to cancel\n&#34;</span>, <span style="color:#a6e22e">name</span>)
				<span style="color:#66d9ef">return</span>
			<span style="color:#66d9ef">default</span>:
				<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%s is running\n&#34;</span>, <span style="color:#a6e22e">name</span>)
				<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">1</span><span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
		}
	}
}

<span style="color:#75715e">//等待前端的退出通知，并试图获取context 传递的数据
</span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">workWithValue</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">name</span> <span style="color:#66d9ef">string</span>){
	<span style="color:#66d9ef">for</span> {
		<span style="color:#66d9ef">select</span> {
			<span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Done</span>():
				<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%s get msg to cancel\n&#34;</span>, <span style="color:#a6e22e">name</span>)
				<span style="color:#66d9ef">return</span>
			<span style="color:#66d9ef">default</span>:
				<span style="color:#a6e22e">value</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Value</span>(<span style="color:#e6db74">&#34;key&#34;</span>).(<span style="color:#66d9ef">string</span>)
				<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%s is running value=%s\n&#34;</span>, <span style="color:#a6e22e">name</span> , <span style="color:#a6e22e">value</span>)
				<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">1</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
		}
	}
}




</code></pre></div><p>结果如下：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">work2 is running
work1 is running
work3 is running value=this is some things
work1 is running
work3 is running value=this is some things
work2 is running
work2 is running
work1 is running
work3 is running value=this is some things
work1 is running
work2 is running
work3 is running value=this is some things
work1 is running
work2 is running
work3 get msg to cancel
work2 is running
work1 get msg to cancel
================
work1 exec cancel ...
================
work2 get msg to cancel
all things done
</code></pre></div><h3 id="分析">分析</h3>
<p>在使用Context的过程中，程序在底层实际上维护了两条关系链。理解这个关系链对于理解context包非常有好处。引用关系链如下：</p>
<ol>
<li>children key 构成了从根到叶子Context 实例的引用关系，这个关系在调用With 函数时，进行维护，调用上完提到的如下函数进行维护：</li>
</ol>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">propagateCancel</span>(<span style="color:#a6e22e">parent</span> <span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">child</span> <span style="color:#a6e22e">canceler</span>) {}
</code></pre></div><p>程序有一层这样的树状结构，本示例是一个链表结构</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">ctxa.children ---&gt; ctxb
		 							 ctxb.children ---&gt; ctxc
</code></pre></div><p>这个树提供一种从根节点开始遍历树的方法。context包的取消广播通知的核心就是基于这一点实现的。</p>
<p>取消通知沿着这条链从根节点向下层节点逐层广播。当然也可以在任意一个子树上发布取消通知，一样会扩散到整棵树。</p>
<p>示例程序中ctxa 收到退出通知。会通知到其绑定到的work2，同时会广播给ctxc 绑定的work3.</p>
<ol start="2">
<li>在构造Context的对象中不断地包裹Context 实例形成的一个引用关系链，这个关系链的方向是相反的，是自底向上的。</li>
</ol>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">ctxc.Context  --&gt; oc
		 ctxc.Context --&gt; ctxb
		 		 ctxc.Context.Context.cancelCtx --&gt; ctxa
		 		 			ctxc.Context.Context.cancelCtx.Context ---&gt; new<span style="color:#f92672">(</span>emptyCtx<span style="color:#f92672">)</span>
</code></pre></div><p>这个关系链主要用来切断当前Context 实例和上层的Context 实例之间的关系。ctxb 调用了退出通知或者定时器到期了。ctxb 后续就没有必要通知广播树继续存在，它需要找到自己的parent,然后执行如下逻辑，把自己从广播树上清理掉。</p>
<pre><code class="language-mermaid" data-lang="mermaid">graph TD
A[emptyCtx]
		--&gt; |context.WithCancel| B[ctxa]
			--&gt; |context.WithDeadline|C[ctxb]
				--&gt; |contextWithValue|D[ctxc]
</code></pre><p>根据上文示例流程整理出使用Context 包的一般流程</p>
<ol>
<li>创建一个Context 根对象。</li>
</ol>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Background</span>() <span style="color:#a6e22e">Context</span>
<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TODO</span>() <span style="color:#a6e22e">Context</span>
</code></pre></div><ol start="2">
<li>包装上一步创建的Context 对象，使其具有特定的功能。</li>
</ol>
<p>这些包装函数是context package 的核心。几乎所有的封装都是从包装函数开始的。原因很简单，使用context 包的核心就是使用其退出广播功能.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">WithCancel</span>(<span style="color:#a6e22e">parent</span> <span style="color:#a6e22e">Context</span>) (<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#a6e22e">CancelFunc</span>) {}
<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">WithDeadline</span>(<span style="color:#a6e22e">parent</span> <span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">d</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span>) (<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">CancelFunc</span>) {}
<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">WithTimeout</span>(<span style="color:#a6e22e">parent</span> <span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">timeout</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Duration</span>) (<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">CancelFunc</span>) {}
<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">WithValue</span>(<span style="color:#a6e22e">parent</span> <span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">val</span> <span style="color:#66d9ef">interface</span>{}) <span style="color:#a6e22e">Context</span> {}
</code></pre></div><ol start="3">
<li>
<p>将上一步创建的对象作为实参传递给后续启动的并发函数（通常作为函数的第一个参数），每个并发函数内部可以继续使用包装函数对传进来的Context 进行包装，添加自己所需要的功能。</p>
</li>
<li>
<p>顶端的goroutine 在超时后调用cancel 退出通知函数。通知树上所有的goroutine 函数释放资源。</p>
</li>
<li>
<p>后端的goroutine 通过chan 监听Context.Done 返回的chan，及时响应前端goroutine 退出通知，一般停止本次处理，释放所占用的资源。</p>
</li>
</ol>
<h2 id="六使用context传递数据的争议">六、使用context传递数据的争议</h2>
<h3 id="该不该使用context-传递数据">该不该使用context 传递数据</h3>
<p>首先要清楚使用context 包主要是解决goroutine的退出通知，传递数据只是一个额外功能。可以使用它传递一些元信息。总之使用context传递的信息不能影响业务的正常逻辑，程序不要期待在context 中传递一些必须的参数等，没有这些参数，程序也应该能够正常工作。</p>
<h3 id="在context-中传递数据的坏处">在context 中传递数据的坏处</h3>
<ol>
<li>传递的都是<code>interface{}</code>类型的值，编译器不能进行严格的类型校验。</li>
<li>从<code>interface{}</code>到具体类型需要使用类型断言和接口查询，有一定的运行期开销和性能损耗</li>
<li>值在传递的过程中，有可能被后续的服务覆盖，且不易被发现。</li>
<li>传递信息不简明，比较晦涩。不能通过代码或者文档一眼看出传递的是什么，不利于后续维护。</li>
</ol>
<h3 id="context-应该传递什么数据">context 应该传递什么数据</h3>
<ol>
<li>日志信息</li>
<li>调试信息</li>
<li>不影响业务逻辑的可选数据</li>
</ol>
<p>context 包提供的核心功能时多个goroutine 之间的退出机制，传递数据只是一个辅助功能，应该谨慎使用context 传递数据。</p>
</div>
        <div class="post-footer">
            <div class="info">
                
                
            </div>
        </div>

        
    </div>


        </div>
    </div>
</div>

<script type="text/javascript"
        src="/js/medium-zoom.min.92f21c856129f84aeb719459b3e6ac621a3032fd7b180a18c04e1d12083f8aba.js"
        integrity="sha256-kvIchWEp&#43;ErrcZRZs&#43;asYhowMv17GAoYwE4dEgg/iro="
        crossorigin="anonymous"></script></body>

</html>
